﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LGPeWJNM.BaseClasses;

namespace LGPeWJNM._2Opt
{
    /// <summary>
    /// Heurística de melhoramento 2-Opt
    /// </summary>
    public class _2Opt : IRoteirizacao
    {
        /// <summary>
        /// Ordena os agrupamentos de clientes a fim de minimizar os trajetos entre eles em seus grupos
        /// </summary>
        /// <remarks>Implementado através da heurística de<i>2-Opt</i> a fim de refinar a solução anterior</remarks>
        /// <param name="destinos">Conjunto de grupos pré-ordenados de coordenadas de destinos</param>
        /// <param name="deposito">Coordenadas do depósito</param>
        /// <param name="quantidadeVeiculos">Número de veículos disponíveis para a roteirização</param>
        /// <param name="capacidadeVeiculo">Capacidade de carga de cada veículo</param>
        /// <returns>Conjunto de grupos ORDENADOS de coordenadas de destinos</returns>
        /// <exception cref="NotImplementedException">É esperado um IEnumerable&lt;IEnumerable&lt;LinkedList.ICliente&gt;&gt; ao invés do identificado na chamda da função</exception>
        public IEnumerable<LinkedList<ICliente>> Processar(IEnumerable<IEnumerable<ICliente>> destinos, Point deposito, int quantidadeVeiculos, float capacidadeVeiculo)
        {
            //if (!(destinos.First() is LinkedList<ICliente>))
            //    throw new NotImplementedException("Apesar da interface indicar um IEnumerable para o conjunto de cliente, é esperado LinkedList pois a mesma já é ordenada.");

            foreach (var cluster in destinos)
            {
                if ((cluster.First() as Cliente).Id != 1 && !cluster.Select(s => (s as Cliente).Id).Contains(1))
                {
                    var clusterLL = new LinkedList<ICliente>(cluster);
                    clusterLL.AddFirst(new Cliente() { Id = 1, Demanda = 0, Coordenada = deposito });
                    clusterLL.AddLast(new Cliente() { Id = 1, Demanda = 0, Coordenada = deposito });
                    yield return OtimizacaoCluster(clusterLL);
                }
                else
                    yield return OtimizacaoCluster(cluster);
            }
        }

        private static LinkedList<ICliente> OtimizacaoCluster(IEnumerable<ICliente> cluster)
        {
            var melhorResultado = new LinkedList<ICliente>(cluster);
            for (int comprimentoTroca = 2; comprimentoTroca < melhorResultado.Count - 1; comprimentoTroca++)
            {
                for (int posicaoTroca = 1; posicaoTroca < melhorResultado.Count - comprimentoTroca; posicaoTroca++)
                {
                    var cl = new LinkedList<ICliente>(melhorResultado);
                    var trechoTroca = cl.Skip(posicaoTroca).Take(comprimentoTroca).ToArray();
                    foreach (var elementoTroca in trechoTroca)
                        cl.Remove(elementoTroca);
                    foreach (var elementoTroca in trechoTroca)//atenção a inversão do vetor devido a esta inserção
                        cl.AddAfter(cl.Find(cl.ElementAt(posicaoTroca - 1)), elementoTroca);
                    var resultadoFinal = cl.CustoTotal();
                    if (cl.CustoTotal() < melhorResultado.CustoTotal())
                    {
                        melhorResultado = new LinkedList<ICliente>(cl);
                        comprimentoTroca = 2;
                        posicaoTroca = 1;
                    }
                }
            }
            return melhorResultado;
        }
    }
}
